Skip to content

Fix #15144 FN knownConditionTrueFalse (binary or) - #8967

Open
chrchr-github wants to merge 3 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15144
Open

chrchr-github wants to merge 3 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15144

Conversation

@chrchr-github

Copy link
Copy Markdown
Collaborator

No description provided.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

The PR introduces a regression in min/max handling for unsigned types. It changes if (Token::simpleMatch(tok, "?") ... to else if in valueFlowImpossibleValues. For an unsigned ?: expression, the preceding astIsUnsigned(tok) branch now consumes the token, so the min/max symbolic impossible values are no longer set. Because of this, unsigned m = a < b ? a : b; if (m > a) {} loses its knownConditionTrueFalse warning. The signed version is still reported. The else should be reverted, since it isn't needed for #15144.

There is also a lesser issue. The new impossible-0 value on | survives conversion to a narrower type, which gives new false positives such as unsigned char c = i | 0x100; if (c) {} and int c = ll | 0x100000000LL; if (c) {}. In both cases the truncated value can be 0. Base already has the same weakness for a plain != 0 that gets narrowed. However, this PR makes it reachable through a common flag-setting idiom, so it may be worth limiting the new value to cases where the non-zero operand fits in the result type.

The added cases (i | 1, i | m after m == 0 returns) work as intended. I found no crashes.

Testing

Both builds succeeded: make -j8 cppcheck testrunner in pr/ and make -j8 cppcheck in base/.

Unit tests: ./testrunner TestCondition TestValueFlow and a full ./testrunner both pass (5351 tests, 0 failures).

Manual comparison (base vs PR): I ran cppcheck -q --enable=style on scratch files. The new true positives work as intended: int c = i | 1; if (c), if (m > 0) { int c = i | m; if (c) }, if (m < 1) return; if ((i | m) != 0) and i | (p != 0) after a null check. if (i | 1) still produces only the badBitmaskCheck warning, with no duplicate.

Regression (false negative) caused by if → else if:

unsigned d(unsigned a, unsigned b) { unsigned m = a < b ? a : b; if (m > a) {} return m; }
int      e(int a, int b)           { int m = a < b ? a : b;      if (m > a) {} return m; }
  • base: t1.cpp:4:72: style: Condition 'm>a' is always false [knownConditionTrueFalse] and the same for line 5
  • PR: only the line 5 (signed) warning remains; the unsigned line 4 warning is gone.

New false positives from narrowing:

void a(int i) { unsigned char c = i | 0x100; if (c) {} }
void b(int i) { if ((unsigned char)(i | 0x100)) {} }
void c(long long i) { int c = i | 0x100000000LL; if (c) {} }
  • PR: reports Condition 'c' is always true for lines 1 and 3 and Condition '(unsigned char)(i|0x100)' is always true for line 2. In all three cases the value can actually be 0.
  • base: reports nothing.

The same narrowing false positive already exists in base for if (i == 0) return; unsigned char c = i; if (c) {}, so this is an existing weakness that the PR exposes more often.


Automated review of 9df4447 by Claude. The pull request was built and tested locally.

Comment thread lib/valueflow.cpp
setTokenValue(tok, std::move(value), settings);
}
if (Token::simpleMatch(tok, "?") && Token::Match(tok->astOperand1(), "<|<=|>|>=")) {
else if (Token::simpleMatch(tok, "?") && Token::Match(tok->astOperand1(), "<|<=|>|>=")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

This else causes a regression. An unsigned ?: token matches the preceding astIsUnsigned(tok) && !astIsPointer(tok) branch, so the min/max handling below is skipped for it. Example: unsigned f(unsigned a, unsigned b) { unsigned m = a < b ? a : b; if (m > a) {} return m; } reports Condition 'm>a' is always false on base but nothing with this PR. The signed version is still reported. #15144 doesn't need this change, so I suggest reverting it to if.

Comment thread lib/valueflow.cpp
value.bound = ValueFlow::Value::Bound::Lower;
value.setImpossible();
setTokenValue(tok, std::move(value), settings);
} else if (tok->isBinaryOp() && tok->str() == "|") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

The impossible 0 is kept when the result is converted to a narrower type, so these are now reported as always true although they can be 0: unsigned char c = i | 0x100; if (c) {}, if ((unsigned char)(i | 0x100)) {} and long long i; int c = i | 0x100000000LL; if (c) {}. Base already has this weakness for a plain != 0 value that is narrowed, but x | FLAG is a common pattern, so it would help to only set the value when a known non-zero operand fits in the type it is converted to (or at least the lower bits).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants